package test.fingers;
/**
 * @author Jesse McCane
 * @author Berkley Shands
 */

/*
 * This class plays the game Fingers and deals with all gameplay related functions
 */

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Gameplay{
	public final static int NUM_SUITS = 4;
	public final static int NUM_VALUES = 13;
	public final static int NUM_CARDS = NUM_SUITS * NUM_VALUES;
	public final static int MIN_CARDS = 2;

	public final static int ALLTRUMP = 1;
	public final static int CLUBS = 2;
	public final static int DIAMONDS = 3;
	public final static int HEARTS = 4;
	public final static int SPADES = 5;
	public final static int NOTRUMP = 6;

	public final static int BID = 1;
	public final static int NIL = 2;
	public final static int PASS = 3;
	public final static int CALL = 4;

	public final static int JACK = 11;
	public final static int QUEEN = 12;
	public final static int KING = 13;
	public final static int ACE = 1;

	public final static int NUM_CALL_OPTIONS = 4;
	public final static int NUM_TRUMP_OPTIONS = 6;
	
	public final static int MAX_PLAYERS = 6;
	public final static int MAX_HANDSIZE = 10;
	public final static int MIN_HANDSIZE = 3;

	public static Map<Integer, String> trumpNames = new HashMap<Integer, String>(); // contains a list of possible trump calls
	public static Map<Integer, String> callNames = new HashMap<Integer, String>(); // contains a list of different bidding calls
	public static Map<Integer, Player> players = new HashMap<Integer, Player>(); // contains a list of players

	private static Deck d = new Deck(NUM_SUITS, NUM_VALUES);
	private static Scanner inputScan = new Scanner(System.in);

	public static int roundTrump, roundTopBidder;
	public static int numPlayers, handSize, numHumans, numComps;

	public static void main(String[] args){
		Card[] hand;
		Player ply;
		String keepPlaying = "y";
		int startRoundPlyNum, startBidPlyNum;

		startBidPlyNum = 0;

		do{
			startRoundPlyNum = adjustForNil(bid(startBidPlyNum)); //run bidding segment and adjust starting player for nilers
			System.out.println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");

			for(int i = 0; i < numPlayers; i++){
				players.get(i).determineStrat();
			}

			for(int playRound = 0; playRound < handSize; playRound++){
				startRoundPlyNum = playTrick(startRoundPlyNum);
				players.get(startRoundPlyNum).addTricksTaken();
				printBids(0, true, true);
			}

			startBidPlyNum = calcScores(); //calc round scores and adjust dealer

			printScores(false);

			System.out.println("Enter 'No' to end the game, or hit enter to keep playing:");
			keepPlaying = inputScan.nextLine();
		}while(!(keepPlaying.toLowerCase().equals("no")));

		printScores(true);
	}
	
	// Anything related to game set up goes here
	public void initGame(){
		Card[] hand;
		Player ply;

		trumpNames.put(ALLTRUMP, "All Trump");
		trumpNames.put(CLUBS, "Clubs");
		trumpNames.put(DIAMONDS, "Diamonds");
		trumpNames.put(HEARTS, "Hearts");
		trumpNames.put(SPADES, "Spades");
		trumpNames.put(NOTRUMP, "No Trump");

		callNames.put(BID, "Bid");
		callNames.put(NIL, "Nil");
		callNames.put(PASS, "Pass");
		callNames.put(CALL, "Call");

		d = new Deck();
		
		initPlayers();
		
		d.shuffleCards();
		for(int i = 0; i < numPlayers; i++){
			ply = players.get(i);
			ply.newRound();
			hand = d.dealHand(handSize);
			ply.receiveHand(hand);
		}
	}
	
	public String[] getUsersHand(){
		String[] hand = players.get(0).getHand();	
		return hand;
	}


	//this function calculates the scores for a round
	//and returns the lowest human scorer
	private static int calcScores(){
		int offPoints;
		int lowestPlayer = 0;
		int lowestScore = players.get(0).getRoundScore();
		Player ply;

		for(int i = 0; i < numPlayers; i++){ //loop through all players and do scoring
			ply = players.get(i);
			offPoints = ply.scoreSelf();
			if(offPoints != 0){
				addToOtherPlayers(offPoints, i);
			}

			ply.displayResults();
		}

		for(int i = 1; i < numHumans; i++){ //loop through to find the lowest round scorer
			ply = players.get(i);
			if(ply.getRoundScore() < lowestScore){
				lowestPlayer = i;
				lowestScore = ply.getRoundScore();
			}
		}

		return lowestPlayer;
	}

	//this function adds the score passed in to all players other than IDtoSkip
	private static void addToOtherPlayers(int score, int IDtoSkip){
		for(int i = 0; i < numPlayers; i++){
			if(i != IDtoSkip){
				players.get(i).addScore(score);
			}
		}
	}

	//this function runs the bidding segment of code
	private static int bid(int startingPlayer){
		int bidCounter = 1;
		int currentMaxBid, currentMaxTrump;
		int currentPlayerID = startingPlayer;
		Player ply = players.get(currentPlayerID);

		// This segment deals with the first bidder
		System.out.println("Welcome to fingers. There are " + handSize + " cards in this hand. \n");

		ply.firstBid();
		currentMaxBid = ply.getTricksBid();
		currentMaxTrump = ply.getSuitBid();

		// this deals with all subsequent bidders
		while(bidCounter < numPlayers){
			currentPlayerID = (currentPlayerID + 1) % numPlayers;

			printBids(currentPlayerID, false, false);

			ply = players.get(currentPlayerID);
			bidCounter = ply.bid(currentMaxTrump, currentMaxBid, bidCounter);

			if(ply.getTricksBid() > currentMaxBid){//outbid in tricks
				currentMaxBid = ply.getTricksBid();
				currentMaxTrump = ply.getSuitBid();
			}else if(ply.getSuitBid() > currentMaxTrump){ //outbid on trump
				currentMaxTrump = ply.getSuitBid();
			}
		}

		System.out.println("Final bids:");
		printBids(currentPlayerID, true, false);

		roundTrump = currentMaxTrump;
		roundTopBidder = (currentPlayerID + 1) % numPlayers;

		return((currentPlayerID + 2) % numPlayers); //do +2 because +1 goes to the highest bidder and +1 more goes to the next person
	}

	//playTrick has every player play a card. The winner of the trick is then determined,
	//and the winning player's tricksTaken is incremented
	private static int playTrick(int startingPlayer){
		Card[] playedCards = new Card[numPlayers];
		int numCardsPlayed = 0;
		Player plyer;
		Card playedCard;
		int currentPlayer = startingPlayer;
		int winningID = currentPlayer;

		for(int i = 0; i < numPlayers; i++){
			plyer = players.get(currentPlayer);

			plyer.setPlayableCards(playedCards, startingPlayer, numCardsPlayed);
			plyer.adjustRoundStrat();
			playedCard = plyer.playTrick();
			playedCards[currentPlayer] = playedCard;
			numCardsPlayed++;

			if((i > 0) && (playedCards[winningID].compareTo(playedCards[currentPlayer]))){
				winningID = currentPlayer;
				System.out.println(plyer.getMyName() + " is winning with a " + playedCard.toString());
			}else{
				System.out.println(plyer.getMyName() + " played a " + playedCard.toString());
			}

			currentPlayer = (currentPlayer + 1) % numPlayers;
		}

		System.out.println("\nCards Played This Round:");
		for(int i = 0; i < playedCards.length; i++){
			System.out.print(players.get(i).myName + ": " + playedCards[i].toString());
			if(i == startingPlayer){
				System.out.print("     <--- Played First");
			}
			System.out.println();
		}
		System.out.println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		System.out.println(players.get(winningID).myName + " won the trick with " + playedCards[winningID].toString() + "\n");
		return winningID;
	}

	private static int adjustForNil(int currentPlayer){
		int count = 0;
		int callType = players.get(currentPlayer).getCallType();

		while(callType == NIL && count <= numPlayers){ //keep going around until we find a non bidder
			currentPlayer = (currentPlayer + 1) % numPlayers;
			callType = players.get(currentPlayer).getCallType();
			count++;
		}

		return currentPlayer;
	}

	//displays the available call options
	public static void printCallOptions(){
		for(int i = 1; i <= NUM_CALL_OPTIONS; i++){
			System.out.println(i + " - " + callNames.get(i));
		}
	}

	//displays the available trump options
	public static void printTrumpOptions(){
		for(int i = 1; i <= NUM_TRUMP_OPTIONS; i++){
			System.out.println(i + " - " + trumpNames.get(i));
		}
	}

	//prints out the current bids
	public static void printBids(int curPlayer, boolean finalPrint, boolean printTricks){
		Player plyer;

		for(int i = 0; i < numPlayers; i++){
			plyer = players.get(i);
			if((!finalPrint) && (curPlayer == i)){
				System.out.print("You (" + plyer.getMyName() + ") are about to bid.     <---");
			}else if(plyer.getCallType() == -1){
				System.out.print(plyer.getMyName() + " has not bid yet.");
			}else if(plyer.getCallType() == NIL){
				System.out.print(plyer.getMyName() + " is currently going nil " + trumpNames.get(plyer.getSuitBid()) + ".");
			}else if(plyer.getCallType() == PASS){
				System.out.print(plyer.getMyName() + " passed.");
			}else{
				System.out.print(plyer.getMyName() + " has bid " + plyer.getTricksBid() + " " + trumpNames.get(plyer.getSuitBid()) + ".");
			}
			if(printTricks){
				System.out.println(" They have taken " + plyer.tricksTaken + " tricks.");
			}else{
				System.out.println();
			}
		}
		System.out.println();
	}

	// anything related to player set up goes here
	private static void initPlayers(){
		numPlayers = numHumans + numComps;

		for(int i = 0; i < numHumans; i++){
			players.put(i, new Human());
		}
		for(int i = numHumans; i < numPlayers; i++){
			players.put(i, new Comp(0, 2));
		}
	}

	/*
	 * This may not be the best way to go about this but it works for now since
	 * you cant have multiple error messages
	 */
	public static int getNumberInput(String prompt, int min, int max){
		boolean errorOccured;
		int inNum = min;

		do{
			errorOccured = false;
			System.out.println(prompt);
			try{
				inNum = Integer.parseInt(inputScan.nextLine());
			}catch (NumberFormatException e){ // catches anything that isnt an int
				System.out.println("You must enter an whole number");
				errorOccured = true;
			}
			if(inNum < min || inNum > max){ // gets the correct range
				System.out.println("You must enter a number between " + min + " and " + max);
				errorOccured = true;
			}
		}while(errorOccured);

		return inNum;
	}

	public static void printScores(boolean finalScore){
		boolean isATie = false;
		int winningScore = players.get(0).getScore();
		int winningPlayer = 0;
		if(finalScore){
			System.out.println(players.get(0).getMyName() + " finished with " + players.get(0).getScore() + " points");
		}else{
			System.out.println(players.get(0).getMyName() + " now has " + players.get(0).getScore() + " points");
		}
		for(int i = 1; i < numPlayers; i++){
			if(finalScore){
				System.out.println(players.get(i).getMyName() + " finished with " + players.get(i).getScore() + " points");
			}else{
				System.out.println(players.get(i).getMyName() + " now has " + players.get(i).getScore() + " points");
			}
			if(players.get(i).getScore() > winningScore){
				isATie = false;
				winningScore = players.get(i).getScore();
				winningPlayer = i;
			}else if(players.get(i).getScore() == winningScore){
				isATie = true;
			}
		}
		if(isATie){
			System.out.print("\n");
			for(int tieLoop = 0; tieLoop < numPlayers; tieLoop++){
				if(players.get(tieLoop).getScore() == winningScore){
					System.out.print(players.get(tieLoop).getMyName() + ", ");
				}
			}
			if(finalScore){
				System.out.println("tied with " + winningScore + " points.");
			}else{
				System.out.println("are tied with " + winningScore + " points.");
			}
		}else{
			if(finalScore){
				System.out.println("\n" + players.get(winningPlayer).getMyName() + " won with a final score of " + winningScore + ". SMASH!");
			}else{
				System.out.println("\n" + players.get(winningPlayer).getMyName() + " is winning with a score of " + winningScore + ". SMASH!");
			}
		}
	}
}
